package com.swanclouds.redfish.internal;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.swanclouds.api.CloudProvider;
import com.swanclouds.api.exceptions.OS4JException;
import com.swanclouds.api.exceptions.ResponseException;
import com.swanclouds.api.types.ServiceType;
import com.swanclouds.core.transport.*;
import com.swanclouds.core.transport.functions.ParseActionResponseFromJsonMap;
import com.swanclouds.core.transport.internal.HttpExecutor;
import com.swanclouds.model.ModelEntity;
import com.swanclouds.model.common.ActionResponse;
import com.swanclouds.model.common.Payload;
import com.swanclouds.model.common.redfish.CollectionResource;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.swanclouds.core.transport.ClientConstants.*;

public class BaseRedfishService {

    private static ThreadLocal<String> reqIdContainer = new ThreadLocal<>();
    private static Map<String,String> etagContainer = new ConcurrentHashMap<>();
    private static ThreadLocal<String> endpointContainer = new ThreadLocal<>();
    private static ThreadLocal<Config> configContainer = new ThreadLocal<>();
    private ServiceType serviceType = ServiceType.IDENTITY;
    private Function<String, String> endpointFunc;

    protected BaseRedfishService() {
        this(ServiceType.IDENTITY);
    }

    protected BaseRedfishService(ServiceType serviceType) {
        this(serviceType, null);
    }

    protected BaseRedfishService(ServiceType serviceType, Function<String, String> endpointFunc) {
        this.serviceType = serviceType;
        this.endpointFunc = endpointFunc;
    }

    public String getXRedfishRequestId() {
        return reqIdContainer.get();
    }

    public String getXRedfishEndpoint() {
        return endpointContainer.get();
    }

    public Config getXRedfishConfig() {
        return configContainer.get();
    }

    protected <R> Invocation<R> get(Class<R> returnType, String... path) {
        return builder(returnType, path, HttpMethod.GET);
    }

    protected Invocation<ActionResponse> getWithResponse(String... path) {
        return builder(ActionResponse.class, path, HttpMethod.GET);
    }

    protected <R> Invocation<R> post(Class<R> returnType, String... path) {
        return builder(returnType, path, HttpMethod.POST);
    }

    protected Invocation<ActionResponse> postWithResponse(String... path) {
        return builder(ActionResponse.class, path, HttpMethod.POST);
    }

    protected <R> Invocation<R> put(Class<R> returnType, String... path) {
        return builder(returnType, path, HttpMethod.PUT);
    }

    protected Invocation<ActionResponse> putWithResponse(String... path) {
        return builder(ActionResponse.class, path, HttpMethod.PUT);
    }

    protected <R> Invocation<R> patch(Class<R> returnType, String... path) {
        return builder(returnType, path, HttpMethod.PATCH);
    }

    protected Invocation<ActionResponse> patchWithResponse(String... path) {
        return builder(ActionResponse.class, path, HttpMethod.PATCH);
    }

    protected <R> Invocation<R> delete(Class<R> returnType, String... path) {
        return builder(returnType, path, HttpMethod.DELETE);
    }

    protected Invocation<ActionResponse> deleteWithResponse(String... path) {
        return builder(ActionResponse.class, path, HttpMethod.DELETE);
    }

    protected <R> Invocation<R> head(Class<R> returnType, String... path) {
        return builder(returnType, path, HttpMethod.HEAD);
    }

    protected <R> Invocation<R> request(HttpMethod method, Class<R> returnType, String path) {
        return builder(returnType, path, method);
    }

    protected String uri(String path, Object... params) {
        if (params.length == 0)
            return path;
        return String.format(path, params);
    }

    private <R> Invocation<R> builder(Class<R> returnType, String[] path, HttpMethod method) {
        return builder(returnType, Joiner.on("").join(path), method);
    }

    private <R> Invocation<R> builder(Class<R> returnType, String path, HttpMethod method) {
        OSClientSession<?, ?> ses = OSClientSession.getCurrent();
        if (ses == null) {
            throw new OS4JException(
                    "Unable to retrieve current session. Please verify thread has a current session available.");
        }
        HttpRequest.RequestBuilder<R> req = HttpRequest.builder(returnType)
                .endpointTokenProvider(ses)
                .config(ses.getConfig())
                .method(method).path(path);
        Map<String, String> headers = ses.getHeaders();
        if (headers != null && headers.size() > 0) {
            return new Invocation<>(req, serviceType, endpointFunc).headers(headers);
        } else {
            return new Invocation<>(req, serviceType, endpointFunc);
        }
    }

    protected <T> List<T> toList(T[] arr) {
        if (arr == null)
            return Collections.emptyList();
        return Arrays.asList(arr);
    }

    protected CloudProvider getProvider() {
        return OSClientSession.getCurrent().getProvider();
    }

    protected static class Invocation<R> {
        HttpRequest.RequestBuilder<R> req;

        protected Invocation(HttpRequest.RequestBuilder<R> req, ServiceType serviceType, Function<String, String> endpointFunc) {
            this.req = req;
            req.serviceType(serviceType);
            req.endpointFunction(endpointFunc);
        }

        public HttpRequest<R> getRequest() {
            return req.build();
        }

        public Invocation<R> param(String name, Object value) {
            req.queryParam(name, value);
            return this;
        }

        public Invocation<R> updateParam(String name, Object value) {
            req.updateQueryParam(name, value);
            return this;
        }

        public Invocation<R> params(Map<String, ?> params) {
            if (params != null) {
                for (String name : params.keySet())
                    req.queryParam(name, params.get(name));
            }
            return this;
        }

        public Invocation<R> param(boolean condition, String name, Object value) {
            if (condition)
                req.queryParam(name, value);
            return this;
        }

        public Invocation<R> paramLists(Map<String, ? extends Iterable<?>> params) {
            if (params != null) {
                for (Map.Entry<String, ? extends Iterable<?>> pair : params.entrySet())
                    for (Object value : pair.getValue())
                        req.queryParam(pair.getKey(), value);
            }
            return this;
        }

        public Invocation<R> serviceType(ServiceType serviceType) {
            req.serviceType(serviceType);
            return this;
        }

        public Invocation<R> entity(ModelEntity entity) {
            req.entity(entity);
            return this;
        }

        public Invocation<R> entity(Object entity) {
            req.entity(entity);
            return this;
        }

        public Invocation<R> entity(Payload<?> entity) {
            req.entity(entity);
            req.contentType(ClientConstants.CONTENT_TYPE_OCTECT_STREAM);
            return this;
        }

        public Invocation<R> contentType(String contentType) {
            req.contentType(contentType);
            return this;
        }

        public Invocation<R> json(String json) {
            req.json(json);
            return this;
        }

        public Invocation<R> headers(Map<String, ?> headers) {
            if (headers != null)
                req.headers(headers);
            return this;
        }

        public Invocation<R> header(String name, Object value) {
            req.header(name, value);
            return this;
        }

        public R execute() {
            return execute(null);
        }

        public R execute(ExecutionOptions<R> options) {
            header(HEADER_USER_AGENT, USER_AGENT);
            String service = req.build().getPath().split("/")[1];
            if (req.build().getMethod() == HttpMethod.PATCH) {
                if (etagContainer.containsKey(service)) {
                    header(ClientConstants.HEADER_IF_MATCH, etagContainer.get(service));
                } else {
                    throw new ResponseException("If-Match not be found", 500);
                }
            }
            HttpRequest<R> request = req.build();
            HttpResponse res = HttpExecutor.create().execute(request);
            if (res.getStatus() == 400 || res.getStatus() == 401 || res.getStatus() == 402 || res.getStatus() == 403 || res.getStatus() == 404 || res.getStatus() == 409 || res.getStatus() == 500 || res.getStatus() == 503) {
                Map<String, Object> map = res.readEntity(Map.class);
                ActionResponse response = new ParseActionResponseFromJsonMap(res).apply(map);
                if (response != null && !response.isSuccess()) {
                    if (response.getFault().indexOf("could not be found") != -1 || response.getFault().indexOf("没有找到") != -1 ||
                            response.getFault().indexOf("does not exist") != -1 || response.getFault().indexOf("不存在") != -1) {
                        if (response.getFault().indexOf("The resource could not be found") != -1) {
                            throw new ResponseException("The resource could not be found", res.getStatus());
                        }
                        throw new ResponseException("could not be found", res.getStatus());
                    }
                    throw new ResponseException(response.getFault(), res.getStatus());
                }
            }

            if (request.getMethod() == HttpMethod.GET) {
                setEtag(service,res);
            }
            endpointContainer.remove();
            endpointContainer.set(getEndpoint(request));

            configContainer.remove();
            configContainer.set(getConfig(request));

            reqIdContainer.remove();
            reqIdContainer.set(getRequestId(res));
            return res.getEntity(request.getReturnType(), options);
        }

        public HttpResponse executeWithResponse() {
            HttpResponse res = HttpExecutor.create().execute(req.build());

            if (req.build().getMethod() == HttpMethod.GET) {
                String service = req.build().getPath().split("/")[1];
                setEtag(service,res);
            }

            endpointContainer.remove();
            endpointContainer.set(getEndpoint(req.build()));

            configContainer.remove();
            configContainer.set(getConfig(req.build()));

            reqIdContainer.remove();
            reqIdContainer.set(getRequestId(res));
            return res;
        }

        private String getETag(HttpResponse res) {
            if (res.headers().containsKey(ClientConstants.HEADER_ETAG)) {
                return res.header(ClientConstants.HEADER_ETAG);
            }
            return null;
        }
        private void setEtag(String service, HttpResponse res) {
            if (etagContainer.containsKey(service)) {
                etagContainer.remove(service);
            }
            String eTag = getETag(res);
            if (eTag != null) {
                etagContainer.put(service, eTag);
            }
        }
        private String getRequestId(HttpResponse res) {
            if (res.headers().containsKey(ClientConstants.HEADER_ETAG)) {
                return res.header(ClientConstants.HEADER_ETAG);
            }
            return null;
        }
        private String getEndpoint(HttpRequest req) {
            return req.getEndpoint();
        }
        private Config getConfig(HttpRequest req) {
            return req.getConfig();
        }
    }

    protected static String getResourceId(CollectionResource resource, ThreadLocal<String> container) {
        String memberId = resource.getCollectionResourceList().get(0);
        String resourceId = memberId.substring(resource.getDataId().length() + 1, memberId.length());
        container.set(resourceId);
        return container.get();
    }
}
